#include "mysql-connect-pool.h"

void *createConnectThread(void *args);          // 创建连接的管理线程

void *destroyConnectThread(void *args);         // 回收连接的管理线程

struct MysqlConnectPool {
    enum MysqlConnectPoolState state;           // 连接池的状态

    int timeout_time;                           // 从连接池获取连接的超时时长(秒)

    int keep_alive_time;                        // MYSQL 连接的存活时间(秒)

    int core_connect_number;                    // MYSQL 核心连接数

    int max_connect_number;                     // MYSQL 最大连接数

    int current_connect_number;                 // MYSQL 当前连接数

    MysqlConnectQueue *queue;                   // 存放 MYSQL 连接的队列

    MysqlConnectFactory *factory;               // 创建 MYSQL 连接的工厂

    pthread_mutex_t pool_mutex;                 // MYSQL 连接池的互斥锁

    pthread_mutex_t queue_mutex;                // MYSQL 连接队列的互斥锁

    pthread_cond_t queue_is_not_empty_cond;     // 连接队列不为空的条件变量

    pthread_t create_connect_thread_pid;        // 创建连接线程的 PID

    pthread_t destroy_connect_thread_pid;       // 销毁连接线程的 PID
};

MysqlConnectPool *
createMysqlConnectPool(char *ip, char *user, char *password, char *database, int port, int timeout_time,
                       int keep_alive_time, int core_connect_number, int max_connect_number) {
    MysqlConnectPool *pool = malloc(sizeof(MysqlConnectPool));

    // 连接池初始状态
    pool->state = Running;
    pool->timeout_time = timeout_time;
    pool->keep_alive_time = keep_alive_time;
    pool->core_connect_number = core_connect_number;
    pool->max_connect_number = max_connect_number;
    pool->current_connect_number = core_connect_number;

    // 连接池需要的连接队列和连接工厂
    pool->queue = createConnectQueue(max_connect_number);
    pool->factory = createMysqlConnectFactory(ip, user, password, database, port);

    // 连接池需要的互斥锁和条件变量
    pthread_mutex_init(&pool->pool_mutex, NULL);
    pthread_mutex_init(&pool->queue_mutex, NULL);
    pthread_cond_init(&pool->queue_is_not_empty_cond, NULL);

    // 创建 MYSQL 连接池的最小连接数
    for (int i = 0; i < core_connect_number; ++i) {
        // 先创建管理线程在这里会有多线程访问的问题
        push(pool->queue, createMysqlConnect(pool->factory));
    }

    // 创建 MYSQL 需要的管理线程
    pthread_create(&pool->create_connect_thread_pid, NULL, createConnectThread, pool);
    pthread_create(&pool->destroy_connect_thread_pid, NULL, destroyConnectThread, pool);

#ifdef DEBUG_MYSQL_CONNECT_POOL
    printf("Create Mysql Connect Pool Success!\n");
#endif
    return pool;
}

void destroyMysqlConnectPool(MysqlConnectPool *pool) {
    pool->state = Shutdown;

    destroyConnectQueue(pool->queue);
    pool->queue = NULL;

    destroyMysqlConnectFactory(pool->factory);
    pool->factory = NULL;

    // 回收 MYSQL 的管理线程(在保证线程不会阻塞在执行过程中的时候, 可以直接使用 join 回收线程)
    pthread_join(pool->create_connect_thread_pid, NULL);
    pthread_join(pool->destroy_connect_thread_pid, NULL);

    // 销毁条件变量和互斥锁
    pthread_mutex_destroy(&pool->pool_mutex);
    pthread_mutex_destroy(&pool->queue_mutex);
    pthread_cond_destroy(&pool->queue_is_not_empty_cond);

    // 释放堆资源
    free(pool);
    pool = NULL;

#ifdef DEBUG_MYSQL_CONNECT_POOL
    printf(" Destroy Mysql Connect Pool Success!\n");
#endif
}

MysqlConnect *getConnect(MysqlConnectPool *pool) {
    MysqlConnect *connect = NULL;

    pthread_mutex_lock(&pool->queue_mutex);

    // 连接队列为空时, 让当前线程阻塞等待
    int count = 10 * pool->timeout_time;
    while (count-- && empty(pool->queue)) {
        // 通知管理线程生产 MYSQL 连接
        pthread_mutex_unlock(&pool->queue_mutex);
        pthread_cond_signal(&pool->queue_is_not_empty_cond);

        // 线程间隔 100 毫秒检查线程池状态
        struct timespec sleep_time = {0, 1000 * 1000 * 100};
        nanosleep(&sleep_time, NULL);

        pthread_mutex_lock(&pool->queue_mutex);
    }

    // count 小于零代表 getConnect 操作已经超时, 直接返回空连接
    if (0 <= count) {
        connect = front(pool->queue);
        pop(pool->queue);
#ifdef DEBUG_PROGRAM
        printf("Get Connect Operation Success!\n");
#endif
    } else {
#ifdef DEBUG_PROGRAM
        printf("Get Connect Operation Timeout, Return Empty Connect!\n");
#endif
    }

    // 通知管理线程生产 MYSQL 连接
    pthread_mutex_unlock(&pool->queue_mutex);
    pthread_cond_signal(&pool->queue_is_not_empty_cond);

    return connect;
}

void freeConnect(MysqlConnectPool *pool, MysqlConnect *connect) {
    pthread_mutex_lock(&pool->queue_mutex);

    // 归还 MYSQL 连接
    if (size(pool->queue) < capacity(pool->queue)) {
        push(pool->queue, connect);
        pthread_mutex_unlock(&pool->queue_mutex);

        return;
    }

    // 一般 MYSQL 连接的数量不会超过队列的容量
    pthread_mutex_unlock(&pool->queue_mutex);

    pthread_mutex_lock(&pool->pool_mutex);
    pool->current_connect_number--;
    pthread_mutex_unlock(&pool->pool_mutex);

    destroyConnect(connect);

#ifdef DEBUG_PROGRAM
    printf("Queue Is Full, Destroy Current Connect!\n");
#endif
}

void *createConnectThread(void *args) {
    MysqlConnectPool *pool = args;

#ifdef DEBUG_MYSQL_CONNECT_POOL
    printf("Create Connect Thread %p Create Success!\n", pthread_self());
#endif

    while (pool->state == Running) {
        pthread_mutex_lock(&pool->queue_mutex);

        // Mysql 连接队列不为空时, 线程休眠阻塞, 等待其他线程唤醒
        while (!empty(pool->queue)) {
            pthread_cond_wait(&pool->queue_is_not_empty_cond, &pool->queue_mutex);
        }

        if (pool->state == Shutdown) {
#ifdef DEBUG_MYSQL_CONNECT_POOL
            printf("Create Connect Thread %p Success Exit!\n", pthread_self());
#endif
            pthread_mutex_unlock(&pool->queue_mutex);
            pthread_exit(NULL);
        }

        // 当前任务队列为空, 但是只有 MYSQL 的总连接数小于最大连接数时才创建新连接
        if (pool->current_connect_number < pool->max_connect_number) {
            push(pool->queue, createMysqlConnect(pool->factory));

            pthread_mutex_lock(&pool->pool_mutex);
            pool->current_connect_number++;
            pthread_mutex_unlock(&pool->pool_mutex);
        }

        pthread_mutex_unlock(&pool->queue_mutex);

        // 线程间隔 100 毫秒检查线程池状态
        struct timespec sleep_time = {0, 1000 * 1000 * 100};
        nanosleep(&sleep_time, NULL);
    }

#ifdef DEBUG_MYSQL_CONNECT_POOL
    printf("Create Connect Thread %p Success Exit!\n", pthread_self());
#endif
    return NULL;
}

void *destroyConnectThread(void *args) {
    MysqlConnectPool *pool = args;

#ifdef DEBUG_MYSQL_CONNECT_POOL
    printf("Destroy Connect Thread %p Create Success!\n", pthread_self());
#endif

    while (pool->state == Running) {
        pthread_mutex_lock(&pool->queue_mutex);
        // 在销毁队列的头节点之前, 队列不能为空
        if (!empty(pool->queue)) {
            // 如果当前 MYSQL 的连接数大于最小连接数, 并且队头连接的空闲时长超过最大空闲时间, 就移除队头的连接
            if (pool->core_connect_number < pool->current_connect_number) {
                if (pool->keep_alive_time + getHeaderAliveTime(pool->queue) <= time(NULL)) {
                    destroyConnect(front(pool->queue));
                    pop(pool->queue);

                    pthread_mutex_lock(&pool->pool_mutex);
                    pool->current_connect_number--;
                    pthread_mutex_unlock(&pool->pool_mutex);
                }
            }
        }
        pthread_mutex_unlock(&pool->queue_mutex);

        // 线程间隔 5 秒检查线程池状态
        struct timespec sleep_time = {0, 1000 * 1000 * 1000};
        sleep_time.tv_nsec *= 5;
        nanosleep(&sleep_time, NULL);
    }

#ifdef DEBUG_MYSQL_CONNECT_POOL
    printf("Destroy Connect Thread %p Success Exit!\n", pthread_self());
#endif
    return NULL;
}